import {MCTSGameMove } from "../mcts_core/MCTSGameMove";
import { GameResult,MCTSGameState } from "../mcts_core/MCTSGameState";
import { GobangMove } from "./GobangMove";
import { GobangPoint } from "./GobangPoint";
import cloneDeep from "lodash/cloneDeep"
/**
 * 落子类型
 */
export enum PieceType {
    Unplaced = 0, // 定义默认值
    Player,
    Computer
}

/**
 * Gobang 游戏状态
 */
export class GobangGameState implements MCTSGameState {
    private boardSideLength: number;
    boardState: GobangPoint[][]; // 棋盘，使用 TypeScript 二维数组
    private unplacedPoints: Set<GobangPoint>;
    lastMove: GobangMove|null;

    /**
     * 创建全新的棋盘
     * @param boardSideLength 棋盘边长
     */
    constructor(boardSideLength: number) {
        this.boardSideLength = boardSideLength;
        this.boardState = new Array(boardSideLength);
        for (let x = 0; x < boardSideLength; x++) {
            this.boardState[x] = new Array(boardSideLength);
            for (let y = 0; y < boardSideLength; y++) {
                const coord = [x, y];
                this.boardState[x][y] = new GobangPoint(coord);
            }
        }

        this.lastMove = null;
        this.unplacedPoints = this.findUnplacedPoints(this.boardState);
    }

    public existsUnexpandedChild(existingChildren: MCTSGameState[]): boolean {
        return this.findUnexpandedPoints(existingChildren).size > 0;
    }

    public expand(existingChildren: MCTSGameState[]): MCTSGameState {
        // 随机选择落子位置
        const unexpandedPoints = this.findUnexpandedPoints(existingChildren);
        const point = this.randomlySelectPoint(unexpandedPoints);

        const nextState = cloneDeep(this);
        nextState.placePiece(new GobangMove(new GobangPoint(point!.coord, PieceType.Computer)));
        return nextState;
    }

    public simulate(): boolean {
        let gameResult = this.judgeLastMove();
        if (gameResult === GameResult.ComputerWon) {
            return true;
        }

        // 建立模拟所需数据结构
        const localGameState = cloneDeep(this);
        let curPieceType: PieceType = PieceType.Computer;
        // 模拟
        do {
            curPieceType = (curPieceType === PieceType.Computer? PieceType.Player : PieceType.Computer);
            const point = this.randomlySelectPoint(localGameState.unplacedPoints);
            if (point === null) {
                return false;
            }
            point.pieceType = curPieceType;
            const move = new GobangMove(point);
            localGameState.placePiece(move);
            gameResult = localGameState.judgeLastMove(); // TODO: check unplacedPoints
        } while (gameResult === GameResult.NoOutcome);
        // let gameState=localGameState
        // gameState.printConsole()
        // console.log(gameResult === GameResult.ComputerWon)
        return gameResult === GameResult.ComputerWon;
    }

    /**
     * 落子时调用的函数
     * 改变落子位置的 PieceType，将落子点从未落子点集中移除，并更新 lastMove
     * @param move 代表本次落子的对象
     */
    placePiece(move: GobangMove): void {
        const coord = move.point.coord;
        this.boardState[coord[0]][coord[1]].pieceType = move.point.pieceType;
        this.unplacedPoints.delete(move.point);
        this.lastMove = move;
    }

    public judgeLastMove(): GameResult {
        return this.judgeMove(this.boardState, this.lastMove!);
    }

    // ========================================== 工具函数或读取器 ==========================================
    public getPiecePointType(x: number, y: number): PieceType {
        return this.boardState[x][y].pieceType;
    }

    public getPieceType(coord: number[]): PieceType {
        return this.getPiecePointType(coord[0], coord[1]);
    }

    public returnLastMove(): MCTSGameMove {
        return this.lastMove!;
    }

    /**
     * 找出棋盘上为空的位置
     * @param curBoardState 当前棋盘状况
     * @returns 未落子位置组成的集合
     */
    private findUnplacedPoints(curBoardState: GobangPoint[][]): Set<GobangPoint> {
        const sideLength = curBoardState.length;
        const unplacedPointSet = new Set<GobangPoint>();
        for (let x = 0; x < sideLength; x++) {
            for (let y = 0; y < sideLength; y++) {
                if (curBoardState[x][y].pieceType === PieceType.Unplaced) {
                    unplacedPointSet.add(curBoardState[x][y]);
                }
            }
        }

        return unplacedPointSet;
    }

    /**
     * 依据传入的子节点，找出可展开的位置
     * @param existingChildren 已经存在的子节点
     * @returns 可展开位置的集合
     */
    private findUnexpandedPoints(existingChildren: MCTSGameState[]): Set<GobangPoint> {
        const unplacedPointsCopy = new Set<GobangPoint>(this.unplacedPoints);
        existingChildren.forEach(mctsChild => {
            const child = mctsChild as GobangGameState;
            unplacedPointsCopy.delete(child.lastMove!.point);
        });

        return unplacedPointsCopy;
    }

    /**
     * 判断此次落子是否导致一方胜利，以及是哪一方胜利
     * 函数不会检查在此次落子之前是否已经有一方胜利
     * @param boardState 落子后的棋盘状态
     * @param move 此次落子
     * @returns 游戏结果：电脑胜利，玩家胜利，胜负未分或平局
     */
    judgeMove(boardState: GobangPoint[][], move: GobangMove): GameResult {
        const lastPieceCoord = move.point.coord;
        const lastPieceX = lastPieceCoord[0];
        const lastPieceY = lastPieceCoord[1];
        const lastPieceType = move.point.pieceType;
        let wins = false;
        // 检查所需的一些边界量
        const startXMin = Math.max(0, lastPieceX - 4);
        const startXMax = Math.min(boardState.length - 1 - 4, lastPieceX);
        let startYMin = Math.max(0, lastPieceY - 4);
        let startYMax = Math.min(boardState.length - 1 - 4, lastPieceY);
        // console.log(lastPieceX,lastPieceY)

        // 横向检查（忽略横纵坐标的转换）
        // ReSharper disable once ConditionIsAlwaysTrueOrFalse
        if (!wins) {
            for (let startX = startXMin; startX <= startXMax; startX++) {
                if (boardState[startX][lastPieceY].pieceType === lastPieceType &&
                    boardState[startX + 1][lastPieceY].pieceType === lastPieceType &&
                    boardState[startX + 2][lastPieceY].pieceType === lastPieceType &&
                    boardState[startX + 3][lastPieceY].pieceType === lastPieceType &&
                    boardState[startX + 4][lastPieceY].pieceType === lastPieceType
                ) {
                    wins = true;
                    break;
                }
            }
        }

        // 纵向检查（忽略横纵坐标的转换）
        if (!wins) {
            for (let startY = startYMin; startY <= startYMax; startY++) {
                if (boardState[lastPieceX][startY].pieceType === lastPieceType &&
                    boardState[lastPieceX][startY + 1].pieceType === lastPieceType &&
                    boardState[lastPieceX][startY + 2].pieceType === lastPieceType &&
                    boardState[lastPieceX][startY + 3].pieceType === lastPieceType &&
                    boardState[lastPieceX][startY + 4].pieceType === lastPieceType
                ) {
                    wins = true;
                    break;
                }
            }
        }

        // 左上向右下检查（假设原点位于左上角，忽略横纵坐标的对换）
        if (!wins) {
            const startPointMaxOffset = Math.min(lastPieceX - startXMin, lastPieceY - startYMin); // 起点距离当前位置的最大偏移量
            const startPointMinOffset = Math.max(lastPieceX - startXMax, lastPieceY - startYMax); // 起点距离当前位置的最小偏移量

            for (let delta = startPointMinOffset; delta <= startPointMaxOffset; delta++) {
                const startX = lastPieceX - delta;
                const startY = lastPieceY - delta;
                if (boardState[startX][startY].pieceType === lastPieceType &&
                    boardState[startX + 1][startY + 1].pieceType === lastPieceType &&
                    boardState[startX + 2][startY + 2].pieceType === lastPieceType &&
                    boardState[startX + 3][startY + 3].pieceType === lastPieceType &&
                    boardState[startX + 4][startY + 4].pieceType === lastPieceType
                ) {
                    wins = true;
                    break;
                }
            }
        }

        // 左下向右上检查（假设原点位于左上角，忽略横纵坐标的对换）
        if (!wins) {
            // 特殊的检查方式，需要更新边界量
            startYMin = Math.max(4, lastPieceY);
            startYMax = Math.min(boardState.length - 1, lastPieceY + 4);
            const startPointMaxOffset = Math.min(lastPieceX - startXMin, startYMax - lastPieceY); // 起点距离当前位置的最大偏移量
            const startPointMinOffset = Math.max(lastPieceX - startXMax, startYMin - lastPieceY); // 起点距离当前位置的最小偏移量

            for (let delta = startPointMinOffset; delta <= startPointMaxOffset; delta++) {
                const startX = lastPieceX - delta;
                const startY = lastPieceY + delta;

                if (boardState[startX][startY].pieceType === lastPieceType &&
                    boardState[startX + 1][startY - 1].pieceType === lastPieceType &&
                    boardState[startX + 2][startY - 2].pieceType === lastPieceType &&
                    boardState[startX + 3][startY - 3].pieceType === lastPieceType &&
                    boardState[startX + 4][startY - 4].pieceType === lastPieceType
                ) {
                    wins = true;
                    break;
                }
            }
        }

        if (wins) {
            return lastPieceType === PieceType.Computer? GameResult.ComputerWon : GameResult.PlayerWon;
        } else {
            const unplacedPoints = this.findUnplacedPoints(boardState); // TODO: check unplacedPoints
            if (unplacedPoints.size === 0) {
                return GameResult.Draw;
            } else {
                return GameResult.NoOutcome;
            }
        }
    }

    /**
     * 从传入点集中随机选择一个点
     * @param points 选择范围（点集）
     * @returns 代表被选中点的对象。如果点集为空，将返回 null
     */
    randomlySelectPoint(points: Set<GobangPoint>): GobangPoint|null {
        if (points.size === 0) {
            return null;
        }
        const pointList = Array.from(points);
        const index = Math.round(Math.random()*(pointList.length-1));
        const selected = pointList[index];
        return selected;
    }
    printConsole(){
        let line='x\\y'
        for(let t=0;t<this.boardSideLength;t++){
            if(t>=10){
                line+=` ${t}`
            }else{
                line+=` ${t} `
            }
        }
        line+='\n'
        for (let x = 0; x < this.boardSideLength; x++) {
            for (let y = 0; y < this.boardSideLength; y++) {
                if(y==0){
                    if(x>=10){
                        line+=` ${x}`
                    }else{
                        line+=` ${x} ` 
                    }
                }
                let point=this.boardState[x][y]
                if(point.pieceType==PieceType.Unplaced){
                    line+=' . '
                }else if(point.pieceType==PieceType.Computer){
                    line+=' x '
                }else if(point.pieceType==PieceType.Player){
                    line+=' o '
                }
                if(y==this.boardSideLength-1){
                    line+='\n'
                }
            }
        }
        console.log(line)
    }
}